﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;

using Caliburn.Micro;

using Cooking.Client.Commanding;
using Cooking.Client.Querying;

using Action = System.Action;

namespace Cooking.Client.Services
{
    public class QueryAgent : IHandle<Command>, IQueryAgent
    {
        private readonly Uri ServiceUri = new Uri("http://localhost:9002/RestService.svc");
        private readonly List<AddNewRecipeCommand> pendingAdditions = new List<AddNewRecipeCommand>();
        private readonly IBusyService busyService;
        private RestContext restContext; 

        public QueryAgent(IEventAggregator eventAggregator, IBusyService busyService)
        {
            this.busyService = busyService;
            eventAggregator.Subscribe(this);
            restContext = new RestContext(ServiceUri);
        }

        public void FindRecipes(int? minimumRating, string partialTitle, string partialDescription,
            Action<IEnumerable<Recipe>> callback)
        {
            string queryString = string.Format(
                "{0}/FindRecipes?partialTitle='{1}'&partialDescription='{2}'&minimumRating={3}",
                ServiceUri, partialTitle, partialDescription, minimumRating);

            busyService.MarkAsBusy();

            restContext.BeginExecute<Recipe>(new Uri(queryString), ar => Complete(() =>
            {
                var recipes =  restContext.EndExecute<Recipe>(ar).ToList();

                AddUnprocessNewRecipes(recipes);

                callback(recipes);
            }), null);
        }

        private void AddUnprocessNewRecipes(ICollection<Recipe> recipes)
        {
            foreach (var pendingAddition in pendingAdditions.ToArray())
            {
                if (!recipes.Any(r => r.Title == pendingAddition.Title))
                {
                    recipes.Add(CreateTemporaryRecipe(pendingAddition));
                }
                else
                {
                    pendingAdditions.Remove(pendingAddition);
                }
            }
        }

        private static Recipe CreateTemporaryRecipe(AddNewRecipeCommand pendingAddition)
        {
            return new Recipe
            {
                Id = -1,
                Title = pendingAddition.Title,
                Description = pendingAddition.Description,
                Rating = null,
                Version = 1
            };
        }

        public void Handle(Command message)
        {
            if (message is AddNewRecipeCommand)
            {
                pendingAdditions.Add((AddNewRecipeCommand)message);
            }
        }

        public object ExecuteQuery<T>(string queryString, Action<IEnumerable<T>> callback)
        {
            queryString = ServiceUri + "/" + queryString;

            return restContext.BeginExecute<T>(new Uri(queryString), ar => Complete(() =>
            {
                var recipes = restContext.EndExecute<T>(ar).ToList();

                callback(recipes);
            }), null);
        }

        public void CancelQuery(object handle)
        {
            restContext.CancelRequest((IAsyncResult) handle);
        }

        private void Complete(Action completionAction)
        {
            Execute.OnUIThread(() =>
            {
                try
                {
                    completionAction();
                }
                catch (DataServiceQueryException exception)
                {
                    busyService.UnmarkAsBusy();

                    if (exception.InnerException != null)
                    {
                        MessageBox.Show(exception.InnerException.Message);
                    }
                    else
                    {
                        MessageBox.Show(exception.Message);
                    }
                }
                catch (Exception exc)
                {
                    if (!RequestWasCanceled(exc))
                    {
                        MessageBox.Show(exc.Message);
                    }
                }
                finally
                {
                    busyService.UnmarkAsBusy();
                }
            });
        }

        private bool RequestWasCanceled(Exception exc)
        {
            var invalidOperationException = exc as InvalidOperationException;
            return ((invalidOperationException != null) && (invalidOperationException.Message.Contains("canceled")));
        }
    }
}